/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <limits.h>
#include <linux/input.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <dirent.h>

#include "bootloader.h"
#include "common.h"
#include "cutils/properties.h"
#include "cutils/android_reboot.h"
#include "install.h"
#include "minui/minui.h"
#include "minzip/DirUtil.h"
#include "roots.h"
#include "efuse.h"
#include "ui.h"
#include "screen_ui.h"
#include "device.h"
#include "adb_install.h"
#include "mtdutils/mtdutils.h"
#include "usb_burning.h"
#include "cmd_excute.h"

extern "C" {
#include "minadbd/adb.h"
#include "fw_env.h"
}

struct selabel_handle *sehandle;
#define REBOOT_NORMAL                        0
#define REBOOT_SHUTDOWN                      1
#define REBOOT_FACTORY_TEST                  2
#define REBOOT_RECOVERY_AGAIN                3

static const struct option OPTIONS[] = {
  { "send_intent", required_argument, NULL, 's' },
  { "update_package", required_argument, NULL, 'u' },
  { "update_patch", required_argument, NULL, 'x' },
  { "reboot_to_factorytest", no_argument, NULL, 'f' },
  { "wipe_data", no_argument, NULL, 'w' },
  { "wipe_cache", no_argument, NULL, 'c' },
  { "usb_burning", no_argument, NULL, 'n' },
  { "without_format", no_argument, NULL, 'o' },   
  { "file_copy_from_partition", required_argument, NULL, 'z' },
  { "factory_backup", no_argument, NULL, 'k' },
#ifdef RECOVERY_HAS_MEDIA
  { "wipe_media", no_argument, NULL, 'm' },
#endif /* RECOVERY_HAS_MEDIA */
  { "show_text", no_argument, NULL, 't' },
  { "just_exit", no_argument, NULL, 'x' },
  { "locale", required_argument, NULL, 'l' },
#ifdef RECOVERY_HAS_EFUSE
  { "set_efuse_version", required_argument, NULL, 'v' },
  { "set_efuse_ethernet_mac", optional_argument, NULL, 'd' },
  { "set_efuse_bluetooth_mac", optional_argument, NULL, 'b' },
#ifdef  EFUSE_LICENCE_ENABLE
  { "set_efuse_audio_license", optional_argument, NULL, 'a' },
#endif /* EFUSE_LICENCE_ENABLE */
#endif /* RECOVERY_HAS_EFUSE */
  { "run_command", required_argument, NULL, 'r' },
  { NULL, 0, NULL, 0 },
};

static const char *COMMAND_FILE = "/cache/recovery/command";
static const char *INTENT_FILE = "/cache/recovery/intent";
static const char *LOG_FILE = "/cache/recovery/log";
static const char *LAST_LOG_FILE = "/cache/recovery/last_log";
static const char *LAST_INSTALL_FILE = "/cache/recovery/last_install";
static const char *LOCALE_FILE = "/cache/recovery/last_locale";
static const char *CACHE_ROOT = "/cache";
static const char *SDCARD_ROOT = "/sdcard";
static const char *FAC_BACKUP_ROOT = "/backup";
static const char *SDCARD_COMMAND_FILE = "/sdcard/factory_update_param.aml";
static const char *UDISK_ROOT = "/udisk";
static const char *UDISK_COMMAND_FILE = "/udisk/factory_update_param.aml";
static const char *UDISK_BACKUP_FILE = "/udisk/backup_factory_update.aml";
static char *FACTORY_TOOLS = "/backup/factory/";
#ifdef RECOVERY_HAS_MEDIA
static const char *MEDIA_ROOT = "/media";
#endif /* RECOVERY_HAS_MEDIA */
static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log";
static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install";
static const char *SIDELOAD_TEMP_DIR = "/tmp/sideload";
static  char *CACHE_BACKUP = "/backup/update.zip";

static  char *INSTALL_RECOVRY_SH = "/system/etc";
static  char *SOURCE_OTACERTZIP = "/res/otacerts.zip";
static  char *DEST_OTACERTZIP = "/system/etc/security/otacerts.zip";

int backup_flag = 0;
static int G_is_need_delete_backup_apk=0;

RecoveryUI* ui = NULL;
int old_updatezip_flag = 0;


char* locale = NULL;
char G_backup_path[100]={0};
#ifdef RECOVERY_HAS_EFUSE
#include "efuse.h"
#endif

//Begin-pptv-add by Gank
#define USE_IMAGE_MENU                        true
bool show_image_menu;
int image_menu_sel; // 1 - 3
static const char *FAC_BACKUP_ROOT_ZIP_FILE = "/backup/update.zip";
static const char *FAC_UDISK_ZIP_FILE = "/udisk/update.zip";
//End


/*
 * The recovery tool communicates with the main system through /cache files.
 *   /cache/recovery/command - INPUT - command line for tool, one arg per line
 *   /cache/recovery/log - OUTPUT - combined log file from recovery run(s)
 *   /cache/recovery/intent - OUTPUT - intent that was passed in
 *
 * The arguments which may be supplied in the recovery.command file:
 *   --send_intent=anystring - write the text out to recovery.intent
 *   --update_package=path - verify install an OTA package file
 *   --wipe_data - erase user data (and cache), then reboot
 *   --wipe_cache - wipe cache (but not user data), then reboot
 *   --set_encrypted_filesystem=on|off - enables / diasables encrypted fs
 *   --just_exit - do nothing; exit and reboot
 *
 * After completing, we remove /cache/recovery/command and reboot.
 * Arguments may also be supplied in the bootloader control block (BCB).
 * These important scenarios must be safely restartable at any point:
 *
 * FACTORY RESET
 * 1. user selects "factory reset"
 * 2. main system writes "--wipe_data" to /cache/recovery/command
 * 3. main system reboots into recovery
 * 4. get_args() writes BCB with "boot-recovery" and "--wipe_data"
 *    -- after this, rebooting will restart the erase --
 * 5. erase_volume() reformats /data
 * 6. erase_volume() reformats /cache
 * 7. finish_recovery() erases BCB
 *    -- after this, rebooting will restart the main system --
 * 8. main() calls reboot() to boot main system
 *
 * OTA INSTALL
 * 1. main system downloads OTA package to /cache/some-filename.zip
 * 2. main system writes "--update_package=/cache/some-filename.zip"
 * 3. main system reboots into recovery
 * 4. get_args() writes BCB with "boot-recovery" and "--update_package=..."
 *    -- after this, rebooting will attempt to reinstall the update --
 * 5. install_package() attempts to install the update
 *    NOTE: the package install must itself be restartable from any point
 * 6. finish_recovery() erases BCB
 *    -- after this, rebooting will (try to) restart the main system --
 * 7. ** if install failed **
 *    7a. prompt_and_wait() shows an error icon and waits for the user
 *    7b; the user reboots (pulling the battery, etc) into the main system
 * 8. main() calls maybe_install_firmware_update()
 *    ** if the update contained radio/hboot firmware **:
 *    8a. m_i_f_u() writes BCB with "boot-recovery" and "--wipe_cache"
 *        -- after this, rebooting will reformat cache & restart main system --
 *    8b. m_i_f_u() writes firmware image into raw cache partition
 *    8c. m_i_f_u() writes BCB with "update-radio/hboot" and "--wipe_cache"
 *        -- after this, rebooting will attempt to reinstall firmware --
 *    8d. bootloader tries to flash firmware
 *    8e. bootloader writes BCB with "boot-recovery" (keeping "--wipe_cache")
 *        -- after this, rebooting will reformat cache & restart main system --
 *    8f. erase_volume() reformats /cache
 *    8g. finish_recovery() erases BCB
 *        -- after this, rebooting will (try to) restart the main system --
 * 9. main() calls reboot() to boot main system
 */

static const int MAX_ARG_LENGTH = 4096;
static const int MAX_ARGS = 100;

// open a given path, mounting partitions as necessary
FILE*
fopen_path(const char *path, const char *mode) {
    if (ensure_path_mounted(path) != 0) {
        LOGE("Can't mount %s\n", path);
        return NULL;
    }

    // When writing, try to create the containing directory, if necessary.
    // Use generous permissions, the system (init.rc) will reset them.
    if (strchr("wa", mode[0])) dirCreateHierarchy(path, 0777, NULL, 1, sehandle);

    FILE *fp = fopen(path, mode);
    return fp;
}

// close a file, log an error if the error indicator is set
void
check_and_fclose(FILE *fp, const char *name) {
    fflush(fp);
    if (ferror(fp)) LOGE("Error in %s\n(%s)\n", name, strerror(errno));
    fclose(fp);
}

// command line args come from, in decreasing precedence:
//   - the actual command line
//   - the bootloader control block (one per line, after "recovery")
//   - the contents of COMMAND_FILE (one per line)
static void
get_args(int *argc, char ***argv) {
    struct bootloader_message boot;
    memset(&boot, 0, sizeof(boot));
    get_bootloader_message(&boot);  // this may fail, leaving a zeroed structure

    if (boot.command[0] != 0 && boot.command[0] != 255) {
        LOGI("Boot command: %.*s\n", sizeof(boot.command), boot.command);
    }

    if (boot.status[0] != 0 && boot.status[0] != 255) {
        LOGI("Boot status: %.*s\n", sizeof(boot.status), boot.status);
    }

    // --- if arguments weren't supplied, look in the bootloader control block
    if (*argc <= 1) {
        boot.recovery[sizeof(boot.recovery) - 1] = '\0';  // Ensure termination
        const char *arg = strtok(boot.recovery, "\n");
        if (arg != NULL && !strcmp(arg, "recovery")) {
            *argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
            (*argv)[0] = strdup(arg);
            for (*argc = 1; *argc < MAX_ARGS; ++*argc) {
                if ((arg = strtok(NULL, "\n")) == NULL) break;
                (*argv)[*argc] = strdup(arg);
            }
            LOGI("Got arguments from boot message\n");
        } else if (boot.recovery[0] != 0 && boot.recovery[0] != 255) {
            LOGE("Bad boot message\n\"%.20s\"\n", boot.recovery);
        }
    }

    // --- if that doesn't work, try the command file form bootloader:recovery_command
    if (*argc <= 1) {
        char *parg = NULL;
        char *recovery_command = fw_getenv("recovery_command");
        if (recovery_command != NULL && strcmp(recovery_command, "")) {
            char *argv0 = (*argv)[0];
            *argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
            (*argv)[0] = argv0;  // use the same program name

            char buf[MAX_ARG_LENGTH];
            strcpy(buf, recovery_command);
            
            if((parg = strtok(buf, "#")) == NULL){
                LOGE("Bad bootloader arguments\n\"%.20s\"\n", recovery_command); 
            }else{
                (*argv)[1] = strdup(parg);  // Strip newline.
                for (*argc = 2; *argc < MAX_ARGS; ++*argc) {
                    if((parg = strtok(NULL, "#")) == NULL){
                        break;
                    }else{
                        (*argv)[*argc] = strdup(parg);  // Strip newline.
                    }
                }
                LOGI("Got arguments from bootloader\n");
            }
            
        } else {
            LOGE("Bad bootloader arguments\n\"%.20s\"\n", recovery_command);
        }
    }

    // --- if that doesn't work, try the command file form bootloader:factory_reset
/*	if (*argc <= 1) {
		char *parg = NULL;
		char *factory_update = fw_getenv ("factory_update");
		LOGE("factory_update\n\"%.20s\"\n", factory_update);
	  if (factory_update != NULL && !strcmp(factory_update, "backup")) {
	    char *argv0 = (*argv)[0];
	    *argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
	    (*argv)[0] = argv0;  // use the same program name
			(*argv)[1] = strdup("--update_package=/backup/update.zip");
			(*argv)[2] = strdup("--wipe_data");
			(*argv)[3] = strdup("--wipe_cache");
			*argc = 4;
	  }
	}*/
    
    // --- if that doesn't work, try the command file
    char * temp_args =NULL;
    if (*argc <= 1) {
        FILE *fp = fopen_path(COMMAND_FILE, "r");
        if (fp != NULL) {
            char *argv0 = (*argv)[0];
            *argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
            (*argv)[0] = argv0;  // use the same program name

            char buf[MAX_ARG_LENGTH];
            for (*argc = 1; *argc < MAX_ARGS; ) {
                if (!fgets(buf, sizeof(buf), fp)) break;
			temp_args = strtok(buf, "\r\n");
			if(temp_args == NULL)  continue;
	       		(*argv)[*argc]  = strdup(temp_args);   // Strip newline.      
                		++*argc;
            }

            check_and_fclose(fp, COMMAND_FILE);
            LOGI("Got arguments from %s\n", COMMAND_FILE);
        }
    }
    // -- sleep 1 second to ensure SD card initialization complete
    usleep(1000000);

    // --- if that doesn't work, try the sdcard command file
    if (*argc <= 1) {
        FILE *fp = fopen_path(SDCARD_COMMAND_FILE, "r");
        if (fp != NULL) {
            char *argv0 = (*argv)[0];
            *argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
            (*argv)[0] = argv0;  // use the same program name

            char buf[MAX_ARG_LENGTH];
            for (*argc = 1; *argc < MAX_ARGS; ) {
                if (!fgets(buf, sizeof(buf), fp)) break;
			temp_args = strtok(buf, "\r\n");
			if(temp_args == NULL)  continue;
	       		(*argv)[*argc]  = strdup(temp_args);   // Strip newline.      
                		++*argc;
            }

            check_and_fclose(fp, SDCARD_COMMAND_FILE);
            LOGI("Got arguments from %s\n", SDCARD_COMMAND_FILE);
        }
    }

    // --- if that doesn't work, try the udisk command file
    if (*argc <= 1) {
        FILE *fp = fopen_path(UDISK_BACKUP_FILE, "r");
        if (fp != NULL) {
            char *argv0 = (*argv)[0];
            *argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
            (*argv)[0] = argv0;  // use the same program name

            char buf[MAX_ARG_LENGTH];
            for (*argc = 1; *argc < MAX_ARGS; ) {
                if (!fgets(buf, sizeof(buf), fp)) break;
			temp_args = strtok(buf, "\r\n");
			if(temp_args == NULL)  continue;
	       		(*argv)[*argc]  = strdup(temp_args);   // Strip newline.      
                		++*argc;
            }

            check_and_fclose(fp, UDISK_BACKUP_FILE);
            LOGI("Got arguments from %s\n", UDISK_BACKUP_FILE);
        }
    }

    // --- if that doesn't work, try the udisk command file
    if (*argc <= 1) {
        FILE *fp = fopen_path(UDISK_COMMAND_FILE, "r");
        if (fp != NULL) {
            char *argv0 = (*argv)[0];
            *argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
            (*argv)[0] = argv0;  // use the same program name

            char buf[MAX_ARG_LENGTH];
            for (*argc = 1; *argc < MAX_ARGS; ) {
                if (!fgets(buf, sizeof(buf), fp)) break;
			temp_args = strtok(buf, "\r\n");
			if(temp_args == NULL)  continue;
	       		(*argv)[*argc]  = strdup(temp_args);   // Strip newline.      
                		++*argc;
            }

            check_and_fclose(fp, UDISK_COMMAND_FILE);
            LOGI("Got arguments from %s\n", UDISK_COMMAND_FILE);
        }
    }

    // --- if no argument, then force show_text
    if (*argc <= 1) {
        char *argv0 = (*argv)[0];
        *argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
        (*argv)[0] = argv0;  // use the same program name
        (*argv)[1] = "--show_text";
        *argc = 2;
    }

    // --> write the arguments we have back into the bootloader control block
    // always boot into recovery after this (until finish_recovery() is called)
    strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
    strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
    int i;
    for (i = 1; i < *argc; ++i) {
        strlcat(boot.recovery, (*argv)[i], sizeof(boot.recovery));
        strlcat(boot.recovery, "\n", sizeof(boot.recovery));
    }
    set_bootloader_message(&boot);
}

static void
set_sdcard_update_bootloader_message() {
    struct bootloader_message boot;
    memset(&boot, 0, sizeof(boot));
    strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
    strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
    set_bootloader_message(&boot);
}

// How much of the temp log we have copied to the copy in cache.
static long tmplog_offset = 0;

void
copy_log_file(const char* source, const char* destination, int append) {
    FILE *log = fopen_path(destination, append ? "a" : "w");
    if (log == NULL) {
        LOGE("Can't open %s\n", destination);
    } else {
        FILE *tmplog = fopen(source, "r");
        if (tmplog != NULL) {
            if (append) {
                fseek(tmplog, tmplog_offset, SEEK_SET);  // Since last write
            }
            char buf[4096];
            while (fgets(buf, sizeof(buf), tmplog)) fputs(buf, log);
            if (append) {
                tmplog_offset = ftell(tmplog);
            }
            check_and_fclose(tmplog, source);
        }
        check_and_fclose(log, destination);
    }
}
void 
copy_otacert_file(const char* source, const char* destination) {
    FILE *des = fopen(destination, "w");
    FILE *tmplog = fopen(source, "r");
    if (tmplog != NULL) {
        fseek(tmplog, 0, SEEK_SET);     
        char buf[4096];
        while(fread(buf,1,sizeof(buf),tmplog)){
            fwrite(buf,sizeof(buf),1,des);  
        }
    }
    fclose(tmplog);
    fclose(des);
}

// clear the recovery command and prepare to boot a (hopefully working) system,
// copy our log file to cache as well (for the system to read), and
// record any intent we were asked to communicate back to the system.
// this function is idempotent: call it as many times as you like.

void finish_recovery(const char *send_intent) {
    // By this point, we're ready to return to the main system...
    if (send_intent != NULL) {
        FILE *fp = fopen_path(INTENT_FILE, "w");
        if (fp == NULL) {
            LOGE("Can't open %s\n", INTENT_FILE);
        } else {
            fputs(send_intent, fp);
            check_and_fclose(fp, INTENT_FILE);
        }
    }

    // Save the locale to cache, so if recovery is next started up
    // without a --locale argument (eg, directly from the bootloader)
    // it will use the last-known locale.
    if (locale != NULL) {
        LOGI("Saving locale \"%s\"\n", locale);
        FILE* fp = fopen_path(LOCALE_FILE, "w");
        fwrite(locale, 1, strlen(locale), fp);
        fflush(fp);
        fsync(fileno(fp));
        check_and_fclose(fp, LOCALE_FILE);
    }

    // Copy logs to cache so the system can find out what happened.
    copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true);
    copy_log_file(TEMPORARY_LOG_FILE, LAST_LOG_FILE, false);
    copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false);
    chmod(LOG_FILE, 0600);
    chown(LOG_FILE, 1000, 1000);   // system user
    chmod(LAST_LOG_FILE, 0640);
    chmod(LAST_INSTALL_FILE, 0644);

    if(backup_flag)
    {
   	if(strlen(G_backup_path)>0)
	{  
		if (ensure_path_mounted(FAC_BACKUP_ROOT) != 0) {
			if (erase_volume(FAC_BACKUP_ROOT)) {
				ui->Print("fac_backup wipe failed.\n");
			}
		}
		ensure_path_mounted(FAC_BACKUP_ROOT);
		copy_custom_data(CACHE_BACKUP, G_backup_path);
		ensure_path_unmounted(FAC_BACKUP_ROOT);
		backup_flag=0;//had copy
        	}
        ui->Print("Copy complete.\n");
    }
	
    if(G_is_need_delete_backup_apk)
   {
		printf("in delete fac_backup apk .....\n");
		ensure_path_mounted("/backup/");
		rm_custom_file(FACTORY_TOOLS);
        	ensure_path_unmounted("/backup/");
		G_is_need_delete_backup_apk=0;
   }
    chmod(CACHE_BACKUP, 0666);
    // Reset to normal system boot so recovery won't cycle indefinitely.
    struct bootloader_message boot;
    memset(&boot, 0, sizeof(boot));
    set_bootloader_message(&boot);

    // Remove the command file, so recovery won't repeat indefinitely.
    if (ensure_path_mounted(COMMAND_FILE) != 0 ||
        (unlink(COMMAND_FILE) && errno != ENOENT)) {
        LOGW("Can't unlink %s\n", COMMAND_FILE);
    }

    ensure_path_unmounted(CACHE_ROOT);
    sync();  // For good measure.
}


#define CUSTOMIZED_DATA_CMD "\"/sbin/recovery.data.sh / /system/customized_data.tar\""

int install_customized_data()
{
	int fd = 0;
	printf("install_customized_data.\n");
	printf("install_customized_data\n");
	ensure_path_mounted("/system");
	ensure_path_mounted("/data");
	ensure_path_mounted("/cache");
	sleep(1);
        int ret = INSTALL_SUCCESS;
        if ((access("/system/customized_data.tar", F_OK) == 0)){
            ret = recovery_run_cmd(CUSTOMIZED_DATA_CMD);
        }else{
            printf("Can not found /system/customized_data.tar. Continue.\n");
	}

        ensure_path_unmounted("/cache");
        ensure_path_unmounted("/data");
        ensure_path_unmounted("/system");
        return ret;
}


int
erase_volume(const char *volume) {
    if(!volume){
        printf("ERR:the volume for erase is NULL! \n");
        return -1;
    }

    printf("start erase volume: %s\n",volume);
    ui->SetBackground(RecoveryUI::ERASING);
    //ui->SetProgressType(RecoveryUI::INDETERMINATE);
    ui->Print("Formatting %s...\n", volume);
    ui->ShowProgress(0.2, 0);
    ensure_path_unmounted(volume);

    if (strcmp(volume, "/cache") == 0) {
        // Any part of the log we'd copied to cache is now gone.
        // Reset the pointer so we copy from the beginning of the temp
        // log.
        tmplog_offset = 0;
    }
    ui->ShowProgress(0.5, 1);
    int result = format_volume(volume);
    if(!result){
        sync();
        Volume* v = volume_for_path(volume);
        printf("format volume:%s sucessed!,now fsync the format device:\n",volume);
        int fd = open(v->device, O_RDWR);
        if (fd < 0) {
            if(v->device)
                printf("open device:%s failed \n",v->device);
            else
                printf("open device failed,this device is NULL\n");
            return -1;
        }
        fsync(fd);
        close(fd);
    }else{
        printf("format volume:%s failed!\n",volume);
	}
	ui->ShowProgress(0.3, 1);
	return result;
}

static char*
copy_sideloaded_package(const char* original_path) {
  if (ensure_path_mounted(original_path) != 0) {
    LOGE("Can't mount %s\n", original_path);
    return NULL;
  }

  if (ensure_path_mounted(SIDELOAD_TEMP_DIR) != 0) {
    LOGE("Can't mount %s\n", SIDELOAD_TEMP_DIR);
    return NULL;
  }

  if (mkdir(SIDELOAD_TEMP_DIR, 0700) != 0) {
    if (errno != EEXIST) {
      LOGE("Can't mkdir %s (%s)\n", SIDELOAD_TEMP_DIR, strerror(errno));
      return NULL;
    }
  }

  // verify that SIDELOAD_TEMP_DIR is exactly what we expect: a
  // directory, owned by root, readable and writable only by root.
  struct stat st;
  if (stat(SIDELOAD_TEMP_DIR, &st) != 0) {
    LOGE("failed to stat %s (%s)\n", SIDELOAD_TEMP_DIR, strerror(errno));
    return NULL;
  }
  if (!S_ISDIR(st.st_mode)) {
    LOGE("%s isn't a directory\n", SIDELOAD_TEMP_DIR);
    return NULL;
  }
  if ((st.st_mode & 0777) != 0700) {
    LOGE("%s has perms %o\n", SIDELOAD_TEMP_DIR, st.st_mode);
    return NULL;
  }
  if (st.st_uid != 0) {
    LOGE("%s owned by %lu; not root\n", SIDELOAD_TEMP_DIR, st.st_uid);
    return NULL;
  }

  char copy_path[PATH_MAX];
  strcpy(copy_path, SIDELOAD_TEMP_DIR);
  strcat(copy_path, "/package.zip");

  char* buffer = (char*)malloc(BUFSIZ);
  if (buffer == NULL) {
    LOGE("Failed to allocate buffer\n");
    return NULL;
  }

  size_t read;
  FILE* fin = fopen(original_path, "rb");
  if (fin == NULL) {
    LOGE("Failed to open %s (%s)\n", original_path, strerror(errno));
    return NULL;
  }
  FILE* fout = fopen(copy_path, "wb");
  if (fout == NULL) {
    LOGE("Failed to open %s (%s)\n", copy_path, strerror(errno));
    return NULL;
  }

  while ((read = fread(buffer, 1, BUFSIZ, fin)) > 0) {
    if (fwrite(buffer, 1, read, fout) != read) {
      LOGE("Short write of %s (%s)\n", copy_path, strerror(errno));
      return NULL;
    }
  }

  free(buffer);

  if (fclose(fout) != 0) {
    LOGE("Failed to close %s (%s)\n", copy_path, strerror(errno));
    return NULL;
  }

  if (fclose(fin) != 0) {
    LOGE("Failed to close %s (%s)\n", original_path, strerror(errno));
    return NULL;
  }

  // "adb push" is happy to overwrite read-only files when it's
  // running as root, but we'll try anyway.
  if (chmod(copy_path, 0400) != 0) {
    LOGE("Failed to chmod %s (%s)\n", copy_path, strerror(errno));
    return NULL;
  }

  return strdup(copy_path);
}

const char**
prepend_title(const char* const* headers) {
    const char* title[] = { "Android system recovery <"
                            EXPAND(RECOVERY_API_VERSION) "e>",
                            "",
                            NULL };

    // count the number of lines in our title, plus the
    // caller-provided headers.
    int count = 0;
    const char* const* p;
    for (p = title; *p; ++p, ++count);
    for (p = headers; *p; ++p, ++count);

    const char** new_headers = (const char**)malloc((count+1) * sizeof(char*));
    const char** h = new_headers;
    for (p = title; *p; ++p, ++h) *h = *p;
    for (p = headers; *p; ++p, ++h) *h = *p;
    *h = NULL;

    return new_headers;
}

int
get_menu_selection(const char* const * headers, const char* const * items,
                   int menu_only, int initial_selection, Device* device) {
    // throw away keys pressed previously, so user doesn't
    // accidentally trigger menu items.
    ui->FlushKeys();

    ui->StartMenu(headers, items, initial_selection);
    int selected = initial_selection;
    int chosen_item = -1;

    while (chosen_item < 0) {
        int key = ui->WaitKey();
        int visible = ui->IsTextVisible();

        if (key == -1) {   // ui_wait_key() timed out
            if (ui->WasTextEverVisible()) {
                continue;
            } else {
                LOGI("timed out waiting for key input; rebooting.\n");
                ui->EndMenu();
                return 0; // XXX fixme
            }
        }

        int action = device->HandleMenuKey(key, visible);

        if (action < 0) {
            switch (action) {
                case Device::kHighlightUp:
                    --selected;
                    selected = ui->SelectMenu(selected);
                    break;
                case Device::kHighlightDown:
                    ++selected;
                    selected = ui->SelectMenu(selected);
                    break;
                case Device::kInvokeItem:
                    chosen_item = selected;
                    break;
                case Device::kNoAction:
                    break;
            }
        } else if (!menu_only) {
            chosen_item = action;
        }
    }

    ui->EndMenu();
    return chosen_item;
}

//Begin-pptv-add by Gank
int
get_image_menu_selection(int initial_selection, Device* device) {
    // throw away keys pressed previously, so user doesn't
    // accidentally trigger menu items.
    ui->FlushKeys();

    int selected = initial_selection;
    int chosen_item = -1;

    while (chosen_item < 0) {
        int key = ui->WaitKey();

        if (key == -1) {   // ui_wait_key() timed out
            if (ui->WasTextEverVisible()) {
                continue;
            } else {
                LOGI("timed out waiting for key input; rebooting.\n");
                return 0; // XXX fixme
            }
        }

        int action = device->HandleMenuKey(key, true);
		printf("Action: %d  -- Key: %d\n", action, key);

        if (action < 0) {
            switch (action) {
                case Device::kMenuLeft:
					--selected;
					selected = ui->SelectImageMenu(selected);
					break;
				case Device::kMenuRight:
					++selected;
					selected = ui->SelectImageMenu(selected);
					break;
				case Device::kHighlightUp:
					selected = selected - 2;
					selected = ui->SelectImageMenu(selected);
					break;
				case Device::kHighlightDown:
					selected = selected + 2;
					selected = ui->SelectImageMenu(selected);
					break;
                case Device::kInvokeItem:
                    chosen_item = selected;
					printf("InvokeItem NO: %d\n", chosen_item);
                    break;
				case Device::kBackDoor:
					chosen_item = 0; //Error Reset UI
					break;
                case Device::kNoAction:
					printf("NoAction\n");
                    break;
            }
        }
    }
    return chosen_item;
}

int
get_txt_menu_selection(const char* const * headers, const char* const * items,
                   int menu_only, int initial_selection, Device* device) {
    // throw away keys pressed previously, so user doesn't
    // accidentally trigger menu items.
    ui->FlushKeys();

    ui->StartMenu(headers, items, initial_selection);
    int selected = initial_selection;
    int chosen_item = -1;

    while (chosen_item < 0) {
        int key = ui->WaitKey();
        int visible = ui->IsTextVisible();

        if (key == -1) {   // ui_wait_key() timed out
            if (ui->WasTextEverVisible()) {
                continue;
            } else {
                LOGI("timed out waiting for key input; rebooting.\n");
                ui->EndMenu();
                return 0; // XXX fixme
            }
        }

        int action = device->HandleMenuKey(key, visible);

        if (action < 0) {
            switch (action) {
                case Device::kHighlightUp:
                    --selected;
                    selected = ui->SelectMenu(selected);
                    break;
                case Device::kHighlightDown:
                    ++selected;
                    selected = ui->SelectMenu(selected);
                    break;
                case Device::kInvokeItem:
                    chosen_item = selected;
                    break;
				case Device::kBackDoor:
					chosen_item = 0; //Error Reset UI
					break;
                case Device::kNoAction:
                    break;
            }
        } else if (!menu_only) {
            chosen_item = action;
        }
    }

    ui->EndMenu();
    return chosen_item;
}

int
get_error_menu_selection(int initial_selection, Device* device) {
    // throw away keys pressed previously, so user doesn't
    // accidentally trigger menu items.
    ui->FlushKeys();
    int chosen_item = -1;
    while (chosen_item < 0) {
        int key = ui->WaitKey();
        if (key == -1) {   // ui_wait_key() timed out
            if (ui->WasTextEverVisible()) {
                continue;
            } else {
                LOGI("timed out waiting for key input; rebooting.\n");
                return 0; // XXX fixme
            }
        }
        int action = device->HandleMenuKey(key, true);
		printf("Action: %d  -- Key: %d\n", action, key);

        if (action < 0) {
            switch (action) {
				case Device::kBackDoor:
					chosen_item = 0; //Error Reset UI
					break;
                case Device::kNoAction:
					printf("NoAction\n");
                    break;
            }
        }
    }
    return chosen_item;
}

int
get_confirm_menu_selection(int initial_selection, Device* device) {
    // throw away keys pressed previously, so user doesn't
    // accidentally trigger menu items.
    ui->FlushKeys();
    int selected = initial_selection;
    int chosen_item = -1;

    while (chosen_item < 0) {
        int key = ui->WaitKey();
        int visible = ui->IsTextVisible();

        if (key == -1) {   // ui_wait_key() timed out
            if (ui->WasTextEverVisible()) {
                continue;
            } else {
                LOGI("timed out waiting for key input; rebooting.\n");
                return 0; // XXX fixme
            }
        }
        int action = device->HandleMenuKey(key, visible);
        if (action < 0) {
            switch (action) {
				case Device::kMenuLeft:
					--selected;
					selected = ui->SelectConfirmMenu(selected);
					break;
				case Device::kMenuRight:
					++selected;
					selected = ui->SelectConfirmMenu(selected);
					break;
                case Device::kInvokeItem:
                    chosen_item = selected;
                    break;
				case Device::kBackDoor:
					chosen_item = 0; //Error Reset UI
					break;
                case Device::kNoAction:
                    break;
            }
        }
    }
    return chosen_item;
}

//End


static int compare_string(const void* a, const void* b) {
    return strcmp(*(const char**)a, *(const char**)b);
}

static int
update_directory(const char* path, const char* unmount_when_done,
                 int* wipe_cache, Device* device) {
    ensure_path_mounted(path);

    const char* MENU_HEADERS[] = { "Choose a package to install:",
                                   path,
                                   "",
                                   NULL };
    DIR* d;
    struct dirent* de;
    d = opendir(path);
    if (d == NULL) {
        LOGE("error opening %s: %s\n", path, strerror(errno));
        if (unmount_when_done != NULL) {
            ensure_path_unmounted(unmount_when_done);
        }
        return 0;
    }

    const char** headers = prepend_title(MENU_HEADERS);

    int d_size = 0;
    int d_alloc = 10;
    char** dirs = (char**)malloc(d_alloc * sizeof(char*));
    int z_size = 1;
    int z_alloc = 10;
    char** zips = (char**)malloc(z_alloc * sizeof(char*));
    zips[0] = strdup("../");

    while ((de = readdir(d)) != NULL) {
        int name_len = strlen(de->d_name);

        if (de->d_type == DT_DIR) {
            // skip "." and ".." entries
            if (name_len == 1 && de->d_name[0] == '.') continue;
            if (name_len == 2 && de->d_name[0] == '.' &&
                de->d_name[1] == '.') continue;

            if (d_size >= d_alloc) {
                d_alloc *= 2;
                dirs = (char**)realloc(dirs, d_alloc * sizeof(char*));
            }
            dirs[d_size] = (char*)malloc(name_len + 2);
            strcpy(dirs[d_size], de->d_name);
            dirs[d_size][name_len] = '/';
            dirs[d_size][name_len+1] = '\0';
            ++d_size;
        } else if (de->d_type == DT_REG &&
                   name_len >= 4 &&
                   strncasecmp(de->d_name + (name_len-4), ".zip", 4) == 0) {
            if (z_size >= z_alloc) {
                z_alloc *= 2;
                zips = (char**)realloc(zips, z_alloc * sizeof(char*));
            }
            zips[z_size++] = strdup(de->d_name);
        }
    }
    closedir(d);

    qsort(dirs, d_size, sizeof(char*), compare_string);
    qsort(zips, z_size, sizeof(char*), compare_string);

    // append dirs to the zips list
    if (d_size + z_size + 1 > z_alloc) {
        z_alloc = d_size + z_size + 1;
        zips = (char**)realloc(zips, z_alloc * sizeof(char*));
    }
    memcpy(zips + z_size, dirs, d_size * sizeof(char*));
    free(dirs);
    z_size += d_size;
    zips[z_size] = NULL;

    int result;
    int chosen_item = 0;
    do {
        chosen_item = get_menu_selection(headers, zips, 1, chosen_item, device);

        char* item = zips[chosen_item];
        int item_len = strlen(item);
        if (chosen_item == 0) {          // item 0 is always "../"
            // go up but continue browsing (if the caller is update_directory)
            result = -1;
            break;
        } else if (item[item_len-1] == '/') {
            // recurse down into a subdirectory
            char new_path[PATH_MAX];
            strlcpy(new_path, path, PATH_MAX);
            strlcat(new_path, "/", PATH_MAX);
            strlcat(new_path, item, PATH_MAX);
            new_path[strlen(new_path)-1] = '\0';  // truncate the trailing '/'
            result = update_directory(new_path, unmount_when_done, wipe_cache, device);
            if (result >= 0) break;
        } else {
            // selected a zip file:  attempt to install it, and return
            // the status to the caller.
            char new_path[PATH_MAX];
            strlcpy(new_path, path, PATH_MAX);
            strlcat(new_path, "/", PATH_MAX);
            strlcat(new_path, item, PATH_MAX);

            ui->Print("\n-- Install %s ...\n", path);
            set_sdcard_update_bootloader_message();
            char* copy = copy_sideloaded_package(new_path);
	     if(strncmp(new_path,FAC_BACKUP_ROOT,strlen(FAC_BACKUP_ROOT))==0)
	     {
	     		printf("this is a backup zip ,need rm tools.apk,factory test apk \n");
			G_is_need_delete_backup_apk=1;
	     }
            if (unmount_when_done != NULL) {
                ensure_path_unmounted(unmount_when_done);
            }
            if (copy) {
                result = install_package(copy, wipe_cache, TEMPORARY_INSTALL_FILE);
				ui->Print("Real Path %s ...\n", copy);
                free(copy);
            } else {
                result = INSTALL_ERROR;
            }
            break;
        }
    } while (true);

    int i;
    for (i = 0; i < z_size; ++i) free(zips[i]);
    free(zips);
    free(headers);

    if (unmount_when_done != NULL) {
        ensure_path_unmounted(unmount_when_done);
    }
    return result;
}

static void
wipe_data(int confirm, Device* device) {
    if (confirm) {
        static const char** title_headers = NULL;

        if (title_headers == NULL) {
            const char* headers[] = { "Confirm wipe of all user data?",
                                      "  THIS CAN NOT BE UNDONE.",
                                      "",
                                      NULL };
            title_headers = prepend_title((const char**)headers);
        }

        const char* items[] = { " No",
                                " No",
                                " No",
                                " No",
                                " No",
                                " No",
                                " No",
                                " Yes -- delete all user data",   // [7]
                                " No",
                                " No",
                                " No",
                                NULL };

        int chosen_item = get_menu_selection(title_headers, items, 1, 0, device);
        if (chosen_item != 7) {
            return;
        }
    }

    ui->Print("\n-- Wiping data...\n");
    device->WipeData();
    erase_volume("/data");
    erase_volume("/cache");
	install_customized_data();
    ui->Print("Data wipe complete.\n");
}

#ifdef RECOVERY_HAS_MEDIA
static void
wipe_media(int confirm,  Device* device) {
    if (confirm) {
        const char** title_headers = NULL;

        if (title_headers == NULL) {
            const char* headers[] = { "Confirm wipe of all media data?",
                                "  THIS CAN NOT BE UNDONE.",
                                "",
                                NULL };
            
            title_headers = prepend_title((const char**)headers);
        }

        char* items[] = { " No",
                          " No",
                          " No",
                          " No",
                          " No",
                          " No",
                          " No",
                          " Yes -- delete all media data",   // [7]
                          " No",
                          " No",
                          " No",
                          NULL };

        int chosen_item = get_menu_selection(title_headers, items, 1, 0, device);
        if (chosen_item != 7) {
            return;
        }
    }

    ui->Print("\n-- Wiping media...\n");
    erase_volume(MEDIA_ROOT);
    ui->Print("Media wipe complete.\n");
}
#endif /* RECOVERY_HAS_MEDIA */

static void ext_update(Device* device) {
	int status = 0;
	int wipe_cache = 0;
	const char** title_headers = NULL;
	const char* headers[] = { "Confirm update?",
                            "  THIS CAN NOT BE UNDONE.",
                            "",
                            NULL };
	title_headers = prepend_title((const char**)headers);
	char* items[] = { " ../",
                 " Update from sdcard",
                 " Update from udisk",
                 " Update from backup",
                  NULL };
	int chosen_item = get_menu_selection(title_headers, items, 1, 0, device);
	if (chosen_item != 1 && chosen_item != 2 &&chosen_item !=3){
	    return;
	}

	switch(chosen_item) {
        case 1:
    		// Some packages expect /cache to be mounted (eg,
    		// standard incremental packages expect to use /cache
    		// as scratch space).
            ensure_path_mounted(CACHE_ROOT);
            status = update_directory(SDCARD_ROOT, SDCARD_ROOT, &wipe_cache, device);
            if (status == INSTALL_SUCCESS && wipe_cache){
                ui->Print("\n-- Wiping cache (at package request)...\n");
                if (erase_volume("/cache")) {
                    ui->Print("Cache wipe failed.\n");
                }else {
                    ui->Print("Cache wipe complete.\n");
                }
            }
            if (status >= 0){
                if (status != INSTALL_SUCCESS){
                    ui->SetBackground(RecoveryUI::ERROR);
                    ui->Print("Installation aborted.\n");
                }else if (!ui->IsTextVisible()){
                    return ;  // reboot if logs aren't visible
                }else{
                    ui->Print("\nInstall from sdcard complete.\n");
                }
            }
            break;

    	case 2:
    	    ensure_path_mounted(CACHE_ROOT);
    	    status = update_directory(UDISK_ROOT, UDISK_ROOT, &wipe_cache, device);
    	    if (status == INSTALL_SUCCESS && wipe_cache){
    	        ui->Print("\n-- Wiping cache (at package request)...\n");
    	        if (erase_volume("/cache")) {
    	            ui->Print("Cache wipe failed.\n");
    	        }else {
    	            ui->Print("Cache wipe complete.\n");
    	        }
    	        }
    	    if (status >= 0) {
    	        if (status != INSTALL_SUCCESS) {
    	            ui->SetBackground(RecoveryUI::ERROR);
    	            ui->Print("Installation aborted.\n");
    	        } else if (!ui->IsTextVisible()) {
    	            return ;  // reboot if logs aren't visible
    	        } else {
    	            ui->Print("\nInstall from udisk complete.\n");
    	        }
    	    }
    	    break;
	case 3:
	     ensure_path_mounted(CACHE_ROOT);
	     status = update_directory(FAC_BACKUP_ROOT, FAC_BACKUP_ROOT, &wipe_cache, device);
	     if (status == INSTALL_SUCCESS && wipe_cache){
		 ui->Print("\n-- Wiping cache (at package request)...\n");
		 if (erase_volume("/cache")) {
		 	ui->Print("Cache wipe failed.\n");
		}else {
			ui->Print("Cache wipe complete.\n");
		}
	    }
    	    if (status >= 0) {
    	        if (status != INSTALL_SUCCESS) {
    	            ui->SetBackground(RecoveryUI::ERROR);
    	            ui->Print("Installation aborted.\n");
    	        } else if (!ui->IsTextVisible()) {
    	            return ;  // reboot if logs aren't visible
    	        } else {
    	            ui->Print("\nInstall from fac_backup complete.\n");
    	        }
    	    }
	    break;
	}
}

//Begin-pptv-add by Gank
static void
wipe_data_at_imagemenu(int confirm, Device* device) {
    if (confirm) {
		printf("===============wipe_data_at_imagemenu================\n");
        ui->SetConfirmMode(true);
		ui->SetConfirmType(RecoveryUI::WIPE_DATA);
		ui->SelectConfirmMenu(1);
        int chosen_item = get_confirm_menu_selection(1, device);
        if (chosen_item != 1) {
            return;
        }
    }
    ui->Print("\n-- Wiping data...\n");
	device->WipeData();
	erase_volume("/data");
	erase_volume("/cache");
	install_customized_data();
	ui->Print("Data wipe complete.\n");
}

static void
wipe_data_plus_at_imagemenu() {
	char *fw_outputmode[] = { "fw_setenv",
						"outputmode", 
						"720p50hz", 
						NULL 
					};
	char *fw_hdmimode[] = { "fw_setenv",
						"hdmimode", 
						"720p50hz", 
						NULL 
					};
	if(fw_setenv(3, fw_outputmode) == 0)
	{
		ui->Print("fw_setenv outputmode 720p50hz.\n");
	}
	if(fw_setenv(3, fw_hdmimode) == 0)
	{
		ui->Print("fw_setenv hdmimode 720p50hz.\n");
	}
	if (ensure_path_mounted("/backup/") != 0)
	{
		ui->Print("Can't mount 'backup', wipe it!\n");
	}
	rm_custom_file(FACTORY_TOOLS);
	ensure_path_unmounted("/backup");
}

static int
update_directory_from_backup_at_imagemenu(const char* unmount_when_done,
                 int* wipe_cache, Device* device) {
	printf("===============update_directory_from_backup_at_imagemenu================\n");
    ui->SetConfirmMode(true);
	ui->SetConfirmType(RecoveryUI::UPDATE_FROM_BACKUP);
	ui->SelectConfirmMenu(1);
    int chosen_item = get_confirm_menu_selection(1, device);
    if (chosen_item != 1) {
        return INSTALL_NONE;
    }
	//Update UI
	ui->SetBackground(RecoveryUI::MENU);
    ui->Print("\n-- Install %s ...\n", FAC_BACKUP_ROOT_ZIP_FILE);
    set_sdcard_update_bootloader_message();
    char* copy = copy_sideloaded_package(FAC_BACKUP_ROOT_ZIP_FILE);
    if (unmount_when_done != NULL) {
        ensure_path_unmounted(unmount_when_done);
    }
	int result;
    if (copy) {
    	result = install_package(copy, wipe_cache, TEMPORARY_INSTALL_FILE);
		ui->Print("Real Path %s ...\n", copy);
        free(copy);
    } else {
        result = INSTALL_ERROR;
    }
    if (unmount_when_done != NULL) {
        ensure_path_unmounted(unmount_when_done);
    }
    return result;
}

static int
apply_path_from_backup_at_imagemenu(int* wipe_cache, Device* device) {
	ensure_path_mounted(CACHE_ROOT);
	int status = update_directory_from_backup_at_imagemenu(FAC_BACKUP_ROOT, wipe_cache, device);
	if (status == INSTALL_SUCCESS && wipe_cache){
		ui->Print("\n-- Wiping cache (at package request)...\n");
		if (erase_volume("/cache")) {
			ui->Print("Cache wipe failed.\n");
		}else {
			ui->Print("Cache wipe complete.\n");
		}
	}
	if (status == INSTALL_SUCCESS) {
		ui->Print("\nInstall from fac_backup complete.\n");
		ui->SetBackground(RecoveryUI::INSTALL_SUCCESS);
	}

	return status;
}

static int
update_directory_from_udisk_at_imagemenu(const char* unmount_when_done,
                 int* wipe_cache, Device* device) {
	printf("===============update_directory_from_backup_at_imagemenu================\n");
    ui->SetConfirmMode(true);
	ui->SetConfirmType(RecoveryUI::UPDATE_FROM_UDISK);
	ui->SelectConfirmMenu(1);
    int chosen_item = get_confirm_menu_selection(1, device);
    if (chosen_item != 1) {
        return INSTALL_NONE;
    }
    ui->Print("\n-- Install %s ...\n", FAC_UDISK_ZIP_FILE);
    set_sdcard_update_bootloader_message();
    int result = install_package(FAC_UDISK_ZIP_FILE, wipe_cache, TEMPORARY_INSTALL_FILE);
    if (unmount_when_done != NULL) {
        ensure_path_unmounted(unmount_when_done);
    }
    return result;
}

static int
apply_path_from_udisk_at_imagemenu(int* wipe_cache, Device* device) {
	ensure_path_mounted(CACHE_ROOT);
	int status = update_directory_from_udisk_at_imagemenu(FAC_BACKUP_ROOT, wipe_cache, device);
	if (status == INSTALL_SUCCESS && wipe_cache){
		ui->Print("\n-- Wiping cache (at package request)...\n");
		if (erase_volume("/cache")) {
			ui->Print("Cache wipe failed.\n");
		}else {
			ui->Print("Cache wipe complete.\n");
		}
	}
	if (status == INSTALL_SUCCESS) {
		ui->Print("\nInstall from fac_backup complete.\n");
		ui->SetBackground(RecoveryUI::INSTALL_SUCCESS);
	}
	return status;
}
//End

static int
prompt_and_wait(Device* device, int status) {
    const char* const* headers = prepend_title(device->GetMenuHeaders());

	int chosen_item = 1;
    for (;;) {
        finish_recovery(NULL);
		ui->SetProgressType(RecoveryUI::EMPTY);
        switch (status) {
            case INSTALL_SUCCESS:
            case INSTALL_NONE:
				//Begin-pptv-add by Gank
				if (show_image_menu) {
					ui->SetImageMode(true);
					ui->SetBackground(RecoveryUI::IMAGE_MENU);
				} else {
					ui->SetBackground(RecoveryUI::MENU);
				}
				//End
                break;

            case INSTALL_ERROR:
            case INSTALL_CORRUPT:
				ui->SetImageMode(false);
                ui->SetBackground(RecoveryUI::ERROR);
                break;
        }
		//Begin-pptv-change by Gank
		int wipe_cache;
		char *fw_outputmode[] = { "fw_setenv",
					"outputmode", 
					"720p50hz", 
					NULL 
				};
		char *fw_hdmimode[] = { "fw_setenv",
					"hdmimode", 
					"720p50hz", 
					NULL 
				};
		if (show_image_menu) {
			if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) {
				/** - Stay at error page wait for KEY_BACK-
				chosen_item = get_error_menu_selection(chosen_item, device);
				if(chosen_item == 0) {
					status = INSTALL_NONE;
				}
				*/
				//Sleep 3S and Auto go back
				sleep(3);
				status = INSTALL_NONE;
				continue;
			} else {
				chosen_item = ui->GetImageMenuSel();
				chosen_item = get_image_menu_selection(chosen_item, device);
				printf("chosen_item=%d\n", chosen_item);
				ui->SetImageMode(false);
				switch (chosen_item) {
					case Device::IMAGE_REBOOT:{
							char *pptv_command = fw_getenv("pptv_command");
					        if (pptv_command != NULL && !strcmp(pptv_command, "updating")) {
								printf("||||||||||||||||||||||||Reboot To Recovery|||||||||||||||||||||||\n");
								return REBOOT_NORMAL;
							} else {
								char *fw_argv[] = { "fw_setenv", "upgrade_step", "2", NULL };
								if(fw_setenv(3, fw_argv) == 0)
								{
									printf("||||||||||||||||||||||||Reboot To System|||||||||||||||||||||||\n");
								}
								sleep(1);//Delay Reboot for Sync fw_setenv();
								return REBOOT_NORMAL;
							}
			            }
						break;
					case Device::IMAGE_WIPE_DATE:
						wipe_data_at_imagemenu(ui->IsTextVisible(), device);
						wipe_data_plus_at_imagemenu();
						break;
					case Device::IMAGE_APPLY_BACKUP:{
							int result = apply_path_from_backup_at_imagemenu(&wipe_cache, device);
							if (result == INSTALL_SUCCESS){
								return REBOOT_NORMAL;
							} else if (result == INSTALL_ERROR || result == INSTALL_CORRUPT){
								status = result;
							}
						}
						break;
					case Device::IMAGE_APPLY_UDISK:{
							int result = apply_path_from_udisk_at_imagemenu(&wipe_cache, device);
							if (result == INSTALL_SUCCESS){
								return REBOOT_NORMAL;
							} else if (result == INSTALL_ERROR || result == INSTALL_CORRUPT){
								status = result;
							}
						}
						break;
					case Device::NO_IMAGE_ACTION:
						break;
				}
			}
		} else {
			chosen_item = get_menu_selection(headers, device->GetMenuItems(), 0, 0, device);

	        // device-specific code may take some action here.  It may
	        // return one of the core actions handled in the switch
	        // statement below.
	        chosen_item = device->InvokeMenuItem(chosen_item);
	        switch (chosen_item) {
	            case Device::REBOOT:
	                return REBOOT_NORMAL;
#ifdef RECOVERY_HAS_FACTORY_TEST
	            case Device::FACTORY_TEST:
			        return REBOOT_FACTORY_TEST;
#endif
	            case Device::WIPE_DATA:
	                wipe_data(ui->IsTextVisible(), device);
			    if(fw_setenv(3, fw_outputmode) == 0)
			   {
				ui->Print("fw_setenv outputmode 720p50hz.\n");
			   }
			    if(fw_setenv(3, fw_hdmimode) == 0)
			   {
				ui->Print("fw_setenv hdmimode 720p50hz.\n");
			   }
			   if (ensure_path_mounted("/backup/") != 0)
			  {
			  	ui->Print("Can't mount 'backup', wipe it!\n");
			  }
			  rm_custom_file(FACTORY_TOOLS);
			  ensure_path_unmounted("/backup");
	                if (!ui->IsTextVisible()) return REBOOT_NORMAL;
	                break;

	            case Device::WIPE_CACHE:
	                //ui->ShowText(false);
	                ui->Print("\n-- Wiping cache...\n");
	                erase_volume("/cache");
	                ui->Print("Cache wipe complete.\n");
	                if (!ui->IsTextVisible()) return REBOOT_NORMAL;
	                break;

#ifdef RECOVERY_HAS_MEDIA
	            case Device::WIPE_MEDIA:
	                wipe_media(ui->IsTextVisible(), device);
	                if (!ui->IsTextVisible()) return REBOOT_NORMAL;
	                break;
#endif /* RECOVERY_HAS_MEDIA */
	            case Device::APPLY_EXT:
	                ext_update(device);
	                break;

	            case Device::APPLY_CACHE:
	                // Don't unmount cache at the end of this.
	                status = update_directory(CACHE_ROOT, NULL, &wipe_cache, device);
	                if (status == INSTALL_SUCCESS && wipe_cache) {
	                    ui->Print("\n-- Wiping cache (at package request)...\n");
	                    if (erase_volume("/cache")) {
	                        ui->Print("Cache wipe failed.\n");
	                    } else {
	                        ui->Print("Cache wipe complete.\n");
	                    }
	                }
	                if (status >= 0) {
	                    if (status != INSTALL_SUCCESS) {
	                        ui->SetBackground(RecoveryUI::ERROR);
	                        ui->Print("Installation aborted.\n");
	                    } else if (!ui->IsTextVisible()) {
	                        return REBOOT_NORMAL;  // reboot if logs aren't visible
	                    } else {
	                        ui->Print("\nInstall from cache complete.\n");
	                    }
	                }
	                break;

	            case Device::APPLY_ADB_SIDELOAD:
	                ensure_path_mounted(CACHE_ROOT);
	                status = apply_from_adb(ui, &wipe_cache, TEMPORARY_INSTALL_FILE);
	                if (status >= 0) {
	                    if (status != INSTALL_SUCCESS) {
	                        ui->SetBackground(RecoveryUI::ERROR);
	                        ui->Print("Installation aborted.\n");
	                    } else if (!ui->IsTextVisible()) {
	                        return REBOOT_NORMAL;  // reboot if logs aren't visible
	                    } else {
	                        ui->Print("\nInstall from ADB complete.\n");
	                    }
	                }
	                break;
#ifdef RECOVERY_HAS_EFUSE
	            case Device::OPERATE_EFUSE:
	                recovery_efuse(-1, NULL, device);
	                if (!ui->IsTextVisible()) return REBOOT_NORMAL;
	                break;
#endif /* RECOVERY_HAS_EFUSE */
	        }
		}
		//End
    }
    return REBOOT_RECOVERY_AGAIN;
}

static void
print_property(const char *key, const char *name, void *cookie) {
    printf("%s=%s\n", key, name);
}

static void
load_locale_from_cache() {
    FILE* fp = fopen_path(LOCALE_FILE, "r");
    char buffer[80];
    if (fp != NULL) {
        fgets(buffer, sizeof(buffer), fp);
        int j = 0;
        unsigned int i;
        for (i = 0; i < sizeof(buffer) && buffer[i]; ++i) {
            if (!isspace(buffer[i])) {
                buffer[j++] = buffer[i];
            }
        }
        buffer[j] = 0;
        locale = strdup(buffer);
        check_and_fclose(fp, LOCALE_FILE);
    }
}

int
main(int argc, char **argv) {
    time_t start = time(NULL);

    // If these fail, there's not really anywhere to complain...
    freopen(TEMPORARY_LOG_FILE, "a", stdout); setbuf(stdout, NULL);
    freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL);

    // If this binary is started with the single argument "--adbd",
    // instead of being the normal recovery binary, it turns into kind
    // of a stripped-down version of adbd that only supports the
    // 'sideload' command.  Note this must be a real argument, not
    // anything in the command file or bootloader control block; the
    // only way recovery should be run with this argument is when it
    // starts a copy of itself from the apply_from_adb() function.
    if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {
        adb_main();
        return 0;
    }

    printf("Starting recovery on %s", ctime(&start));

    load_volume_table();
    get_args(&argc, &argv);

    int previous_runs = 0;
    const char *send_intent = NULL;
    const char *update_package = NULL;
    int wipe_data = 0, wipe_cache = 0, show_text = 0;
    const char *update_patch = NULL;
    char *file_copy_from_partition_args = NULL;
    int reboot_to_factorymode = 0;
	int usb_burning = 0;
	int without_format = 0;
#ifdef RECOVERY_HAS_MEDIA
    int wipe_media = 0;
#endif /* RECOVERY_HAS_MEDIA */
#ifdef RECOVERY_HAS_EFUSE
    const char *efuse_version = NULL;
    int set_efuse_version = 0;
    int set_efuse_ethernet_mac = 0;
    int set_efuse_bluetooth_mac = 0;
#ifdef EFUSE_LICENCE_ENABLE
    int set_efuse_audio_license = 0;
#endif /* EFUSE_LICENCE_ENABLE */
#endif /* RECOVERY_HAS_EFUSE */
    bool just_exit = false;
    int run_cmd = 0;
    char *cmd_args = NULL;

    int arg;
    while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
        switch (arg) {
        case 'p': previous_runs = atoi(optarg); break;
        case 's': send_intent = optarg; break;
        case 'u': update_package = optarg; break;
	case 'x': update_patch = optarg; break;
	case 'k': backup_flag=1; break;
        case 'w': wipe_data = wipe_cache = 1; break;
        case 'c': wipe_cache = 1; break;
        case 't': show_text = 1; break;
        //case 'x': just_exit = true; break;
        case 'l': locale = optarg; break;
        case 'f': reboot_to_factorymode = 1; break;
		case 'n': usb_burning = 1; break;
		case 'o': without_format = 1; break;	
	case 'z': file_copy_from_partition_args = optarg; break;
#ifdef RECOVERY_HAS_MEDIA
        case 'm': wipe_media = 1; break;
#endif /* RECOVERY_HAS_MEDIA */
#ifdef RECOVERY_HAS_EFUSE
        case 'v': set_efuse_version = 1; efuse_version = optarg; break;
        case 'd': set_efuse_ethernet_mac = 1; break;
        case 'b': set_efuse_bluetooth_mac = 1; break;
#ifdef EFUSE_LICENCE_ENABLE
        case 'a': set_efuse_audio_license = 1; break;
#endif /* EFUSE_LICENCE_ENABLE */

#endif /* RECOVERY_HAS_EFUSE */

        case 'e': just_exit = true; break;
        case 'r': run_cmd = 1; cmd_args = optarg; break;
        case '?':
            LOGE("Invalid command argument\n");
            continue;
        }
    }

    if (locale == NULL) {
        load_locale_from_cache();
    }
    printf("locale is [%s]\n", locale);

    Device* device = make_device();
    ui = device->GetUI();

    ui->Init();
    ui->SetLocale(locale);
	ui->SetBackground(RecoveryUI::NONE);
    if (show_text) ui->ShowText(true);

	//Begin-pptv-add by Gank
	show_image_menu = USE_IMAGE_MENU;
	//End

#ifdef HAVE_SELINUX
    struct selinux_opt seopts[] = {
      { SELABEL_OPT_PATH, "/file_contexts" }
    };

    sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1);

    if (!sehandle) {
        fprintf(stderr, "Warning: No file_contexts\n");
        ui->Print("Warning:  No file_contexts\n");
    }
#endif

    device->StartRecovery();

    printf("Command:");
    for (arg = 0; arg < argc; arg++) {
        printf(" \"%s\"", argv[arg]);
    }
    printf("\n");

	char *pptv_command = fw_getenv("pptv_command");
	char *amlogic_command = fw_getenv("upgrade_step");
	
	printf("PPTV Command: %s  -- Amlogic Command: %s\n", pptv_command, amlogic_command);

    /**
     *  Disable auto reformat, we should *NOT* do this.
     *
     *  For /media partition, we cannot do it because this will break
     *  any file system that's non-FAT.
     * 
     *  For /data & /cache, If it is yaffs, format or not is ok, yaffs can be mounted even haven`t been format,
     *  If it is ubifs or ext4, it is necessary to format it, make sure it can be mounted if user didn`t do that.
	 *  It is recommended to add wipe data/cache in factory_update_param.aml, but uncomment below code
	 *  
     */
#if 1
    if (ensure_path_mounted("/data") != 0) {
        ui->Print("Can't mount 'data', wipe it!\n");
        if (erase_volume("/data")) {
            ui->Print("Data wipe failed.\n");
        }
    }

    if (ensure_path_mounted("/cache") != 0) {
        ui->Print("Can't mount 'cache', wipe it!\n");
        if (erase_volume("/cache")) {
            ui->Print("Cache wipe failed.\n");
        }
    }
    if (ensure_path_mounted("/backup") != 0) {
        ui->Print("Can't mount 'backup', wipe it!\n");
        if (erase_volume("/backup")) {
            ui->Print("backup wipe failed.\n");
        }
    }
	ensure_path_unmounted("/backup");

#ifdef RECOVERY_HAS_MEDIA
    if (ensure_path_mounted(MEDIA_ROOT) != 0) {
        ui->Print("Can't mount 'media', wipe it!\n");
        if (erase_volume(MEDIA_ROOT)) {
            ui->Print("Media wipe failed.\n");
        }
    }
#endif
#endif /* 0 */

    if(file_copy_from_partition_args)
    {
        char *file_path = NULL;
        char *partition_type = NULL;
        char *partition = NULL;
        char *size_str = NULL;

        if(((file_path = strtok(file_copy_from_partition_args, ":")) == NULL)
            ||((partition_type = strtok(NULL, ":")) == NULL) 
            ||((partition = strtok(NULL, ":")) == NULL)  
            ||((size_str = strtok(NULL, ":")) == NULL))
        {
            printf("file_copy_from_partition_args Invalid!\n");
        }
        else
        {
            ssize_t file_size = atoi(size_str);
            file_copy_from_partition(file_path, partition_type, partition, file_size);
        }
    }
    
    if (update_package) {
        // For backwards compatibility on the cache partition only, if
        // we're given an old 'root' path "CACHE:foo", change it to
        // "/cache/foo".
        if (strncmp(update_package, "CACHE:", 6) == 0) {
            int len = strlen(update_package) + 10;
            char* modified_path = (char*)malloc(len);
            strlcpy(modified_path, "/cache/", len);
            strlcat(modified_path, update_package+6, len);
            printf("(replacing path \"%s\" with \"%s\")\n",
                   update_package, modified_path);
            update_package = modified_path;
        }
    }
    printf("\n");

    property_list(print_property, NULL);
    printf("\n");

    int status = INSTALL_SUCCESS;

	if(usb_burning)
	{
		int opt;
		opt = without_format;
		usb_burning_main(opt);
	}

    if (update_package != NULL) {
	  if(strncmp(update_package,FAC_BACKUP_ROOT,strlen(FAC_BACKUP_ROOT)) == 0) {
           printf("update package this is a backup zip ,need rm tools.apk,factory test apk \n");
           G_is_need_delete_backup_apk = 1;
        }
        status = install_package(update_package, &wipe_cache, TEMPORARY_INSTALL_FILE);
        if (status == INSTALL_SUCCESS) {
	     if(backup_flag == 0){
		     ui->SetBackground(RecoveryUI::INSTALL_SUCCESS);
		     ui->SetProgressType(RecoveryUI::EMPTY);
		     sleep(5);
	     }
	     memset(G_backup_path,0,sizeof(G_backup_path));
	     strcpy(G_backup_path,update_package);
             sleep(3);
	     printf("update sucessfully, remove update zip file ! \n");
	     rm_custom_file("/cache/update.zip");
	     if(wipe_cache){
	            if (erase_volume("/cache")) {
	                LOGE("Cache wipe (requested by package) failed.");
	            }
	     }
        }
        if (status != INSTALL_SUCCESS) ui->Print("Installation aborted.\n");
    }
    if (update_patch != NULL) {
	  if(strncmp(update_package,FAC_BACKUP_ROOT,strlen(FAC_BACKUP_ROOT)) == 0) {
           printf("update package this is a backup zip ,need rm tools.apk,factory test apk \n");
           G_is_need_delete_backup_apk = 1;
        }
        status = install_package(update_patch, &wipe_cache, TEMPORARY_INSTALL_FILE);
        if (status != INSTALL_SUCCESS) ui->Print("Installation patch aborted.\n");
	 else{
	 	if(backup_flag == 0){
		 	ui->SetBackground(RecoveryUI::INSTALL_SUCCESS);
			ui->SetProgressType(RecoveryUI::EMPTY);
			sleep(5);
	 	}
	 }
    }
    if (wipe_data) {
        if (erase_volume("/data")) status = INSTALL_ERROR;
        if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
        if (status != INSTALL_SUCCESS) ui->Print("Data wipe failed.\n");
		else
		{
			char *fw_outputmode[] = { "fw_setenv",
				"outputmode", 
				"720p50hz", 
				NULL 
			};
			 char *fw_hdmimode[] = { "fw_setenv",
				"hdmimode", 
				"720p50hz", 
				NULL 
			};
			if(fw_setenv(3, fw_outputmode) == 0)
			{
				ui->Print("fw_setenv outputmode 720p50hz.\n");
			}
			if(fw_setenv(3, fw_hdmimode) == 0)
			{
				ui->Print("fw_setenv hdmimode 720p50hz.\n");
			}
			if (install_customized_data()) status = INSTALL_ERROR;
		}
	 if (ensure_path_mounted("/backup/") != 0)
	{
		ui->Print("Can't mount 'backup', wipe it!\n");
	}
	rm_custom_file(FACTORY_TOOLS);
	ensure_path_unmounted("/backup");
    }
    if (wipe_cache) {
        if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
        if (status != INSTALL_SUCCESS) ui->Print("Cache wipe failed.\n");
    }

    if (wipe_data) {
	char *wipedata_step = fw_getenv("wipedata_step");
        if((wipedata_step != NULL) && (!strcmp(wipedata_step, "1")))
        {
             char *wp_argv[] = { "fw_setenv", "wipedata_step", "2", NULL };
             char *up_argv[] = { "fw_setenv", "upgrade_step", "2", NULL };
             if((fw_setenv(3, wp_argv) == 0) && (fw_setenv(3, up_argv) == 0))
             {
                 printf("wipe data partition successfully ! set wipedata_step and upgrade_step to 2 !\n");
             }
        }
    }

//    else if (!just_exit) {
//        status = INSTALL_NONE;  // No command specified
//        ui->SetBackground(RecoveryUI::NO_COMMAND);
//    }
#ifdef RECOVERY_HAS_MEDIA
    if (wipe_media) {
        if (wipe_media && erase_volume(MEDIA_ROOT)) status = INSTALL_ERROR;
        if (status != INSTALL_SUCCESS) ui->Print("Media wipe failed.\n");
    } 
#endif /* RECOVERY_HAS_MEDIA */    
#ifdef RECOVERY_HAS_EFUSE
    if (set_efuse_version) {
        status = recovery_efuse(EFUSE_VERSION, efuse_version, device);
    }
#ifdef EFUSE_LICENCE_ENABLE
    if (set_efuse_audio_license) {
        status = recovery_efuse(EFUSE_LICENCE, NULL, device);
    }
#endif /* EFUSE_LICENCE_ENABLE */

    if (set_efuse_ethernet_mac) {
        status = recovery_efuse(EFUSE_MAC, NULL, device);
    }

    if (set_efuse_bluetooth_mac) {
        status = recovery_efuse(EFUSE_MAC_BT, NULL, device);
    }
#endif /* RECOVERY_HAS_EFUSE */
    if (run_cmd) {
    	ui->Print("run command %s\n", cmd_args);
    	status = recovery_run_cmd(cmd_args);
    }
    int howReboot;
    /*if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) {
        ui->SetBackground(RecoveryUI::ERROR);
    }*/
    if (status != INSTALL_SUCCESS || ui->IsTextVisible()) {
        ui->ShowText(true);
        howReboot = prompt_and_wait(device, status);
        if (REBOOT_FACTORY_TEST == howReboot)
            reboot_to_factorymode = 1;
    }

    // Otherwise, get ready to boot the main system...
    finish_recovery(send_intent);
    if(old_updatezip_flag==1)
    {
        if (ensure_path_mounted(INSTALL_RECOVRY_SH) == 0) {
            unlink("/system/etc/install-recovery.sh");  
            copy_otacert_file(SOURCE_OTACERTZIP,DEST_OTACERTZIP);
        }
        ensure_path_unmounted(INSTALL_RECOVRY_SH);

    }
    ui->Print("Rebooting...\n");
    sync();
    if (reboot_to_factorymode) {
        property_set("androidboot.mode", "factorytest");
        android_reboot(ANDROID_RB_RESTART2, 0, "factory_testl_reboot");
    } else {
        android_reboot(ANDROID_RB_RESTART2, 0, "normal_reboot");
    }
    return EXIT_SUCCESS;
}
